<template>
	<el-card class="card">
		<div class="flowBox" style="height: 100%">
			<div class="flowTopBox" v-loading="saveLoading">
				<div class="leftFlowBox">
					<el-scrollbar scroll-y class="scroll">
						<div class="flowdiv">
							<el-input v-model="filterCatalogueText" placeholder="search" /><br /><br />
							<el-tree
								ref="catalogueTreeRef"
								highlight-current
								:data="catalogueTreeList"
								default-expand-all
								node-key="id"
								:filter-node-method="filterNode"
							>
								<template #default="{ node, data }">
									<div v-if="data.ifLeaf == '1'" class="flow-tree-node">
										<span>
											<img v-show="data.ifLeaf == '1'" :src="folderPng" />
											<span style="margin-left: 8px">{{ data.name }}</span>
										</span>
									</div>
									<div v-else @mousedown="startDrag(data)" class="flow-tree-drag—node">
										<span>
											<img v-show="data.taskType == '4'" :src="FlinkEnvPng" />
											<img v-show="data.taskType == '2'" :src="FlinkPng" />
											<img v-show="data.taskType == '1'" :src="SqlPng" />
											<img v-show="data.taskType == '5'" :src="Datax" />
											<span style="margin-left: 8px">{{ data.name }}</span>
										</span>
									</div>
								</template>
							</el-tree>
						</div>
					</el-scrollbar>
				</div>
				<div class="rightFlowBox">
					<div style="height: 100%; width: 100%" @dragover="allowDropOver" ref="flowContainer" id="flowContainer"></div>
				</div>
			</div>
			<!-- 上下侧边 -->
			<div v-show="runFlag" title="上下侧边栏" class="flowBottomResize"></div>
			<div class="flowBottomBox" v-show="runFlag">
				<el-tabs class="flowBottomTabs">
					<el-tab-pane>
						<template #label>
							<span class="flowCustomTabsLabel">
								<el-icon><Postcard /></el-icon>&nbsp;
								<span>日志信息</span>
							</span>
						</template>
						<read-only-studio id="flowConsoleLog" ref="flowConsoleLogRef" style="height: 100%"></read-only-studio>
					</el-tab-pane>
				</el-tabs>
			</div>
		</div>
	</el-card>

	<!-- 下载流程图 -->
	<div id="snapshotDownloadId" v-show="false"></div>

	<!-- 节点框 -->
	<el-drawer v-model="nodeVisable" direction="rtl" :append-to-body="true">
		<template #header>
			<h4>编辑调度</h4>
		</template>
		<template #default>
			<el-form ref="nodeDataFormRef" :model="nodeDataForm" :rules="nodeDataRules" label-width="100px" @keyup.enter="nodeSubmit()">
				<el-form-item label="名称" prop="properties.name" label-width="auto">
					<el-input disabled v-model="nodeDataForm.properties.name" placeholder="名称"></el-input>
				</el-form-item>
				<el-form-item label="作业类型" prop="properties.taskTypeVal" label-width="auto">
					<el-input disabled v-model="nodeDataForm.properties.taskTypeVal" placeholder="作业类型"></el-input>
				</el-form-item>
				<el-form-item label="权重" prop="properties.weight" label-width="auto">
					<el-input-number v-model="nodeDataForm.properties.weight" :min="1" :max="100"></el-input-number>
				</el-form-item>
				<el-form-item label="遇错是否继续" prop="properties.failGoOn" label-width="auto">
					<fast-select v-model="nodeDataForm.properties.failGoOn" dict-type="yes_or_no" placeholder="请选择"></fast-select>
				</el-form-item>
				<el-form-item label="备注" prop="properties.note" label-width="auto">
					<el-input :rows="2" type="textarea" v-model="nodeDataForm.properties.note" placeholder="备注"></el-input>
				</el-form-item>
			</el-form>
		</template>
		<template #footer>
			<el-button @click="nodeVisable = false">取消</el-button>
			<el-button type="primary" @click="nodeSubmit()">确定</el-button>
		</template>
	</el-drawer>

	<!-- 调度任务保存 -->
	<el-dialog v-model="configVisible" title="调度配置" @keyup.enter="scheduleSubmit(false)">
		<el-form ref="scheduleFormRef" :model="scheduleForm" :rules="scheduleRules" label-width="100px">
			<el-form-item label="所属调度组" prop="name" label-width="auto">
				<el-select v-model="scheduleForm.groupId" placeholder="所属调度组">
					<el-option v-for="item in scheduleForm.projectList" :key="item.value" :label="item.label" :value="item.value" />
				</el-select>
			</el-form-item>
			<el-form-item label="调度任务名称" prop="name" label-width="auto">
				<el-input v-model="scheduleForm.name" placeholder="调度任务名称"></el-input>
			</el-form-item>
			<el-form-item label="周期执行" prop="ifCycle" label-width="auto">
				<fast-select v-model="scheduleForm.ifCycle" dict-type="yes_or_no" placeholder="请选择" clearable filterable></fast-select>
			</el-form-item>
			<el-form-item label="cron表达式" prop="cron" label-width="auto" v-if="scheduleForm.ifCycle == '1'">
				<el-input v-model="scheduleForm.cron" placeholder="cron表达式" notrim @blur="notrim(scheduleForm.cron)"></el-input>
				<!-- <el-popover ref="cronPopover" :width="550" trigger="click">
						<Cron @submit="changeCron" @close="cronPopover.hide()"></Cron>
						<template #reference>
							<el-input v-model="scheduleForm.cron" placeholder="cron表达式"></el-input>
						</template>
					</el-popover> -->
			</el-form-item>
			<el-form-item label="备注" prop="note" label-width="auto">
				<el-input :rows="2" type="textarea" v-model="scheduleForm.note" placeholder="备注"></el-input>
			</el-form-item>
		</el-form>
		<template #footer>
			<el-button @click="configVisible = false">取消</el-button>
			<el-button type="primary" @click="scheduleSubmit(false)">确定</el-button>
		</template>
	</el-dialog>
</template>

<script setup lang="ts" name="Data-developmentScheduleFlow">
import { reactive, ref, onMounted, watch, nextTick } from 'vue'
import LogicFlow from '@logicflow/core'
import { Menu, Control, SelectionSelect, DndPanel, Snapshot } from '@logicflow/extension'
import '@logicflow/core/dist/style/index.css'
import '@logicflow/extension/lib/style/index.css'
import TaskModel from '../schedule/UserTaskNode.js'
import FlowLink from '../schedule/FlowLink.js'
import FlinkPng from '../../../assets/flink.png'
import SqlPng from '../../../assets/sql.png'
import FlinkEnvPng from '../../../assets/flinkenv.png'
import Datax from '../../../assets/IDTL.png'
import folderPng from '../../../assets/folder.png'
import { useCatalogueListApi } from '@/api/data-development/catalogue'
import { ElMessage, ElMessageBox } from 'element-plus/es'
import store from '@/store'
import { getDictLabel } from '@/utils/tool'
import Cron from '@/components/fast-cron/index'
import { useScheduleSubmitApi, useScheduleApi, runFlowApi, getConsoleLogApi, getNodeInfoApi } from '@/api/data-development/schedule'
import ReadOnlyStudio from '../production/readonly-studio.vue'
import { ElNotification } from 'element-plus'
import { ScheduleGroupList } from '@/api/auth-hm'

//cron表达式，流程配置
const emit = defineEmits(['refreshDataList'])
const configVisible = ref(false)
const cronPopover = ref()
const scheduleFormRef = ref()

const graphData = {
	nodes: [],
	edges: []
}
const runFlag = ref(false)
const init = (id: number, ifRun: any) => {
	// 重置表单数据
	scheduleForm.id = ''
	scheduleForm.name = ''
	scheduleForm.ifCycle = ''
	scheduleForm.cron = ''
	scheduleForm.note = ''
	scheduleForm.status = ''
	scheduleForm.releaseTime = ''
	scheduleForm.releaseUserId = ''
	scheduleForm.groupId = ''
	scheduleForm.nodes = []
	scheduleForm.edges = []
	flowConsoleLogRef.value.setEditorValue('')
	flowEnd.value = false
	var bottomResize = document.getElementsByClassName('flowBottomResize')
	var topBox = document.getElementsByClassName('flowTopBox')
	var downBox = document.getElementsByClassName('flowBottomBox')
	bottomResize[0].style.height = '0'
	topBox[0].style.height = '100%'
	downBox[0].style.height = '0'
	//如果id不为空，给scheduleForm赋值
	if (id) {
		useScheduleApi(id).then(res => {
			Object.assign(scheduleForm, res.data)
			graphData.nodes = scheduleForm.nodes
			graphData.edges = scheduleForm.edges
			//渲染
			lf.render(graphData)
			if (ifRun) {
				bottomResize[0].style.height = '10px'
				topBox[0].style.height = '70%'
				downBox[0].style.height = 'calc(30% - 10px)'
				runFlag.value = true
				runFlow(id)
			}
		})
	} else {
		//渲染
		graphData.nodes = []
		graphData.edges = []
		lf.render(graphData)
	}
}

const notrim = val => {
	scheduleForm.cron = val.trim()
}

const changeCron = (val: any) => {
	scheduleForm.cron = val
}

const scheduleForm = reactive({
	id: '',
	name: '',
	ifCycle: '',
	cron: '',
	note: '',
	nodes: [],
	edges: [],
	groupId: '',
	projectList: []
})

const scheduleRules = ref({
	name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
	ifCycle: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
	cron: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
})

const saveLoading = ref(false)
//保存流程
const scheduleSubmit = (submit: any) => {
	if (submit) {
		//校验
		if (!scheduleForm.name || !scheduleForm.groupId || scheduleForm.ifCycle === '' || (scheduleForm.ifCycle == '1' && !scheduleForm.cron)) {
			ElMessage.warning('请点击配置，配置完调度信息再保存！')
			return
		}
		saveLoading.value = true
		let graphData = lf.getGraphData()
		scheduleForm.nodes = graphData.nodes
		scheduleForm.edges = graphData.edges
		scheduleForm.cron = scheduleForm.ifCycle == '1' ? scheduleForm.cron : ''

		useScheduleSubmitApi(scheduleForm)
			.then(() => {
				ElMessage.success({
					message: '保存成功',
					duration: 500,
					onClose: () => {
						//刷新分页
						emit('refreshDataList')
					}
				})
			})
			.finally(() => {
				saveLoading.value = false
			})
	} else {
		scheduleFormRef.value.validate((valid: boolean) => {
			if (!valid) {
				return false
			}
			ElMessage.success({
				message: '操作成功',
				duration: 500,
				onClose: () => {
					configVisible.value = false
				}
			})
		})
	}
}

//节点编辑框
const nodeVisable = ref(false)
const nodeDataFormRef = ref()

const nodeDataForm = reactive({
	id: '',
	properties: {
		name: '',
		taskType: '',
		taskTypeVal: '',
		taskId: '',
		note: '',
		failGoOn: '',
		weight: 1
	},
	type: '',
	x: 490,
	y: 230
})

const nodeDataRules = ref({
	properties: {
		name: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
		taskTypeVal: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
		failGoOn: [{ required: true, message: '必填项不能为空', trigger: 'blur' }],
		weight: [{ required: true, message: '必填项不能为空', trigger: 'blur' }]
	}
})

let lf = null
onMounted(() => {
	nextTick(() => {
		ScheduleGroupListFun()
		//初始化上下拖动
		dragControllerUD()
		//加载作业目录树
		getCatalogueTreeList()
		//加载流程引擎
		lf = new LogicFlow({
			container: document.querySelector('#flowContainer'),
			grid: {
				visible: true,
				type: 'mesh',
				size: 10,
				config: {
					color: '#eeeeee'
				}
			},
			keyboard: {
				enabled: true
			},
			// 注册组件
			plugins: [Menu, Control, SelectionSelect, DndPanel, Snapshot]
		})
		// lf.updateEditConfig({
		// 	isSilentMode: false
		// })
		//注册任务节点
		lf.register(TaskModel)
		//注册连线
		lf.register(FlowLink)
		lf.setDefaultEdgeType('flow-link')
		document.getElementById('snapshotDownloadId').addEventListener('click', () => {
			lf.getSnapshot()
		})
		//左侧面板
		lf.extension.dndPanel.setPatternItems([
			{
				label: '开启选区',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					lf.openSelectionSelect()
					/* lf.once("selection:selected", () => {
			        lf.closeSelectionSelect();
			      }); */
				}
			},
			{
				label: '关闭选区',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					lf.closeSelectionSelect()
				}
			},
			{
				label: '清空画布',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					lf.clearData()
				}
			},
			{
				label: '配置',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					configVisible.value = true
				}
			},
			{
				label: '保存',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					let graphData = lf.getGraphData()
					scheduleForm.nodes = graphData.nodes
					scheduleForm.edges = graphData.edges
					scheduleSubmit(true)
				}
			},
			{
				label: '导出',
				className: 'dnd-class',
				icon: '',
				callback: () => {
					document.getElementById('snapshotDownloadId').dispatchEvent(new MouseEvent('click'))
				}
			}
		])

		lf.setTheme({
			/* rect: {
			    fill: '#fffae1',
			    stroke: '#000000',
			    strokeWidth: 2,
			  }, */
			snapline: {
				stroke: '#ace2ff', // 对齐线颜色
				strokeWidth: 1 // 对齐线宽度
			}
		})
		//设置右键菜单
		lf.extension.menu.setMenuConfig({
			nodeMenu: [
				{
					text: '查看',
					callback(node) {
						nodeEdit(node)
					}
				},
				{
					text: '删除',
					callback(node) {
						lf.deleteNode(node.id)
					}
				}
			], // 覆盖默认的节点右键菜单
			//edgeMenu: false, // 删除默认的边右键菜单
			graphMenu: [] // 覆盖默认的边右键菜单，与false表现一样
		})
		//监听新增的拖拽节点
		lf.on('node:dnd-add', data => {
			nodeEdit(data.data)
		})
		//渲染
		//lf.render(graphData)
	})
})
const ScheduleGroupListFun = () => {
	scheduleForm.projectList = []
	ScheduleGroupList({}).then((res: any) => {
		if (res.code == 0) {
			let dataList = res.data && res.data.length > 0 ? res.data : []
			let dataObj = []
			for (let i = 0; i < dataList.length; i++) {
				let obj = {
					value: '',
					label: ''
				}
				obj.value = dataList[i].id
				obj.label = dataList[i].groupName
				dataObj.push(obj)
			}
			scheduleForm.projectList = dataObj
		} else {
			ElMessage.error(res.msg)
		}
	})
}
const catalogueTreeRef = ref()
const catalogueTreeList = ref<Tree[]>([])
const filterCatalogueText = ref('')
const NodeAddOrUpdateRef = ref()
/**
 * 获取目录树
 */
const getCatalogueTreeList = () => {
	useCatalogueListApi().then(res => {
		catalogueTreeList.value = res.data
	})
}

watch(filterCatalogueText, val => {
	catalogueTreeRef.value!.filter(val)
})

/**
 * 节点筛选
 */
const filterNode = (value: string, data: Tree) => {
	if (!value) return true
	return data.label.includes(value) || data.label.includes(value.toUpperCase()) || data.label.includes(value.toLowerCase())
}

const allowDropOver = ev => {
	ev.preventDefault()
}
//添加呗拖拽的节点
const startDrag = item => {
	if (item.taskType == '1' || item.taskType == '2' || item.taskType == '5') {
		lf.dnd.startDrag({
			type: 'UserTask',
			properties: {
				name: item.name,
				taskType: item.taskType,
				taskTypeVal: getDictLabel(store.appStore.dictList, 'production_task_type', item.taskType),
				taskId: item.taskId,
				note: '',
				weight: 1,
				failGoOn: 0
				/* ,
					style: { border: '3px solid #e30000'} */
			}
		})
	} else {
		ElMessage.warning('只能拖动 sql 或 flinkSql 类型的节点')
	}
}

/**
 * 编辑节点
 */
const nodeEdit = (data: any) => {
	nodeVisable.value = true
	nodeDataForm.id = ''

	// 重置表单数据
	if (nodeDataFormRef.value) {
		nodeDataFormRef.value.resetFields()
	}

	//赋值
	Object.assign(nodeDataForm, data)
}

/**
 *  保存节点到lf
 */
const nodeSubmit = () => {
	nodeDataFormRef.value.validate((valid: boolean) => {
		if (!valid) {
			return false
		}
		lf.getNodeModelById(nodeDataForm.id).setProperties(nodeDataForm.properties)
		ElMessage.success('操作成功')
		nodeVisable.value = false
	})
}

/**
 * 运行流程
 */
const flowConsoleLogRef = ref()
const runFlow = async id => {
	//提示
	ElNotification({
		title: '提示',
		message: '正在提交流程。。。',
		duration: 0,
		zIndex: 9999,
		type: 'success'
	})
	//获取recordId
	const { data } = await runFlowApi(id)
	//console.log(data)
	ElNotification.closeAll()
	//提示
	ElNotification({
		title: '提示',
		message: '正在执行流程，请耐心等待。。。',
		duration: 0,
		zIndex: 9999,
		type: 'success'
	})
	//获取日志
	getConsoleLog(data)
	//设置节点状态
	setNodeStatus(data)
}

//流程结束标识
const flowEnd = ref(false)
//每1s获取一次日志
const getConsoleLog = (recordId: any) => {
	getConsoleLogApi(recordId).then(res => {
		//设置log
		if (res.data.log != flowConsoleLogRef.value.getEditorValue()) {
			flowConsoleLogRef.value.setEditorValue(res.data.log)
		}
		//如果没结束，再次调用
		if (!res.data.end) {
			setTimeout(() => {
				getConsoleLog(recordId)
			}, 1500)
		} else {
			flowEnd.value = true
		}
	})
}

//设置每个节点的状态，成功-绿色 失败-红色
const setNodeStatus = (recordId: any) => {
	if (!flowEnd.value) {
		setNodeInfo(recordId)
		//递归
		setTimeout(() => {
			setNodeStatus(recordId)
		}, 1500)
	} else {
		setNodeInfo(recordId)
		ElMessage.success('调度流程执行完毕，请前往调度记录查看具体执行情况！')
		ElNotification.closeAll()
	}
}

const setNodeInfo = (recordId: any) => {
	getNodeInfoApi(recordId).then(res => {
		let nodeList = res.data
		for (let i in nodeList) {
			const nodeInfo = nodeList[i]
			const nodeModel = lf.getNodeModelById(nodeInfo.scheduleNodeNo)
			if (nodeModel) {
				const properties = nodeModel.getProperties()
				if (nodeInfo.runStatus == 3) {
					properties.style = { border: '3px solid #06c733' }
					//关闭动画
					if (nodeInfo.edgeId) {
						lf.closeEdgeAnimation(nodeInfo.edgeId)
					}
				} else if (nodeInfo.runStatus == 4) {
					properties.style = { border: '3px solid #e30000' }
					//关闭动画
					if (nodeInfo.edgeId) {
						lf.closeEdgeAnimation(nodeInfo.edgeId)
					}
				} else {
					//开启边的动画效果
					if (nodeInfo.edgeId) {
						lf.openEdgeAnimation(nodeInfo.edgeId)
					}
				}
				nodeModel.setProperties(properties)
			}
		}
	})
}

// 上下拖动事件
const dragControllerUD = () => {
	var bottomResize = document.getElementsByClassName('flowBottomResize')
	var topBox = document.getElementsByClassName('flowTopBox')
	var downBox = document.getElementsByClassName('flowBottomBox')
	var box = document.getElementsByClassName('flowBox')
	for (let i = 0; i < bottomResize.length; i++) {
		// 鼠标按下事件
		bottomResize[i].onmousedown = function (e) {
			console.log(bottomResize[i].top)
			//颜色改变提醒
			bottomResize[i].style.background = '#818181'
			var startY = e.clientY
			bottomResize[i].top = bottomResize[i].offsetTop
			// 鼠标拖动事件
			document.onmousemove = function (e) {
				var endY = e.clientY
				var moveLen = bottomResize[i].top + (endY - startY) // （endY - startY）=移动的距离。resize[i].top+移动的距离=上边区域最后的高度
				var maxT = box[i].clientHeight - bottomResize[i].offsetHeight // 容器高度 - 上边区域的高度 = 下边区域的高度

				if (moveLen < 100) moveLen = 100 // 上边区域的最小高度为100px
				if (moveLen > maxT - 80) moveLen = maxT - 80 //下边区域最小高度为80px

				bottomResize[i].style.top = moveLen // 设置上边区域的高度

				for (let j = 0; j < topBox.length; j++) {
					topBox[j].style.height = moveLen + 'px'
					downBox[j].style.height = box[i].clientHeight - moveLen - bottomResize[i].offsetHeight + 'px'
				}
			}
			// 鼠标松开事件
			document.onmouseup = function () {
				//颜色恢复
				bottomResize[i].style.background = '#d6d6d6'
				document.onmousemove = null
				document.onmouseup = null
				bottomResize[i].releaseCapture && bottomResize[i].releaseCapture() //当你不在需要继续获得鼠标消息就要应该调用ReleaseCapture()释放掉
			}
			bottomResize[i].setCapture && bottomResize[i].setCapture() //该函数在属于当前线程的指定窗口里设置鼠标捕获
			return false
		}
	}
}

defineExpose({
	init,
	runFlow
})
</script>

<style lang="scss" scoped>
#flowContainer {
	height: 100%;
	width: 100%;
	/* height: calc(100vh - 200px); */
}
.dnd-class {
	padding: 7px;
}
/* 主矿体 */
.flowTopBox {
	height: 70%;
	display: flex;
}
/* 左侧布局 */
.leftFlowBox {
	height: 100%;
	flex: 1;
	.scroll {
		height: 100%;
	}
}
/* 右侧布局 */
.rightFlowBox {
	height: 100%;
	flex: 5;
}
/* 拖拽区div样式 */
.flowBottomResize {
	overflow: hidden;
	cursor: s-resize;
	width: 100%;
	height: 10px;
	background-color: #d6d6d6;
	margin: 0 auto;
	border-radius: 5px;
	text-align: center;
	line-height: 3px;
	font-size: 32px;
	color: white;
}
/* 底部box */
.flowBottomBox {
	height: 32%;
}
/* 树节点相关属性 */
.flowdiv .el-tree-node__content {
	height: 35px;
}
.flow-tree-node {
	font-size: 16px;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}
.flow-tree-drag—node {
	font-size: 16px;
	cursor: move;
	-webkit-user-select: none;
	-khtml-user-select: none;
	-moz-user-select: none;
	-ms-user-select: none;
	user-select: none;
}

.flowBottomTabs {
	height: 100%;
}

.flowBottomTabs > .el-tabs__content {
	height: calc(100% - 40px);
	padding: 0;
}
/* 每个tab高度设置为100% */
.flowBottomTabs ::v-deep(.el-tabs__content) {
	height: 100% !important;
}
.flowBottomTabs > .el-tabs__content > .el-tab-pane {
	height: 100%;
}

.flowBottomTabs .flowCustomTabsLabel {
	font-size: 16px;
}
.flowBottomTabs > .el-tabs__header {
	padding: 0;
	position: relative;
	margin: 0 0 0;
}
#flowConsoleLog .el-notification.right {
	height: 100px;
}
.card {
	height: 100%;
	::v-deep(.el-card__body) {
		padding: 0 !important;
	}
}
</style>
